Skip to content

Add memory map to the debugger backend (#96)#1113

Merged
xusheng6 merged 6 commits into
devfrom
test_implement_96_memory_map
Jul 8, 2026
Merged

Add memory map to the debugger backend (#96)#1113
xusheng6 merged 6 commits into
devfrom
test_implement_96_memory_map

Conversation

@xusheng6

@xusheng6 xusheng6 commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

First step toward #96 (and #27): expose the target's memory map — every mapped region of the virtual address space with its permissions — through the debugger, mirroring the existing modules feature end to end.

A memory region is deliberately a separate abstraction from a module, not an extension of it:

  • a module (one entry per loaded binary) typically spans several regions with different permissions (.text r-x, .data rw-, …);
  • many regions — stack, heap, anonymous mmap — belong to no module at all;
  • on macOS the relationship is many-to-one both ways (the dyld shared cache packs many modules' __TEXT into one region and shares a single __LINKEDIT), so neither can be cleanly derived from the other.

So this adds a new DebugMemoryRegion type and a new GetMemoryMap() adapter API rather than overloading DebugModule.

What's included

Layer Change
core DebugMemoryRegion struct; DebugAdapter::GetMemoryMap() (default-empty so adapters opt in incrementally); DebuggerMemoryMap state cache (marked dirty on every stop, fetched lazily); DebuggerController::GetMemoryMap()
adapters LLDB via SBProcess::GetMemoryRegions(); GDB via /proc/[pid]/maps, parsed per region (with rwx/shared) instead of aggregated by path the way GetModuleList does
FFI BNDebugMemoryRegion + BNDebuggerGetMemoryMap / BNDebuggerFreeMemoryRegions
C++ API DebuggerController::GetMemoryMap()
Python DebugMemoryRegion + controller.memory_map

DebugMemoryRegion carries {start, size, name, read, write, execute, shared}, where name is a backing file path, a pseudo-name like [stack]/[heap], or empty for anonymous mappings.

Scope / deliberately out of scope

This is the data foundation only. It does not change the debugger's live BinaryView: the flat remote region added in CreateDebuggerBinaryView still backs reads, so readability is unaffected on every platform.

The follow-up — replacing that flat region with per-region segments (real lengths + rwx permissions), which is the behavioral goal of #96/#27 — is intentionally a separate PR. Reads today go straight to the adapter (DebuggerFileAccessor::ReadReadMemory) and are not gated by segments; the flip to per-region backing should land only after the map is validated as complete on each platform, so that no byte that is actually readable becomes unreadable. Landing the map first makes that validation possible (open the panel / call controller.memory_map and compare against real readability).

Adapters other than LLDB/GDB (DbgEng, etc.) inherit the default-empty GetMemoryMap() and return an empty map until implemented — no behavior change for them.

Testing

  • Builds and links cleanly (ninja debuggercore debuggerapi) on macOS.
  • Python modules byte-compile.
  • Manual verification of the region data on live targets (LLDB/GDB) is the main thing reviewers should exercise; that's also the validation gate before the per-region-segment follow-up.

🤖 Generated with Claude Code

@xusheng6

xusheng6 commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

Added a Debugger Memory Map sidebar widget (commit c9416c0) so the map can be exercised in the UI, not just via controller.memory_map.

  • Mirrors the Debugger Modules panel: columns Start / End / Size / Permissions / Name (permissions shown as e.g. r-xp / rw-s).
  • Refreshes on the target-stopped event; supports filtering, Copy / Copy All, and double-click / context-menu Jump To Start / End.
  • Appears in the right sidebar (RightBottom, hidden when there's no active debug session), same as the Modules panel.

How to test: launch/attach a target (LLDB or GDB), pause it, open the Debugger Memory Map sidebar icon. Compare against /proc/<pid>/maps (Linux) or vmmap <pid> (macOS) to sanity-check completeness — that comparison is also the validation gate for the per-region-segment follow-up. Other adapters (DbgEng, etc.) show an empty panel until GetMemoryMap() is implemented for them.

xusheng6 and others added 6 commits July 8, 2026 12:57
Introduce a memory-map abstraction that exposes the target's mapped
virtual-address regions with their permissions, mirroring the existing
modules feature end to end.

A memory region is distinct from a module: a module (one entry per loaded
binary) typically spans several regions with different permissions, and
many regions (stack, heap, anonymous mmap) belong to no module at all. So
this is a new `DebugMemoryRegion` type and a new `GetMemoryMap()` adapter
API rather than an extension of the module list.

Layers added:
  - core: DebugMemoryRegion struct + DebugAdapter::GetMemoryMap() (default
    empty so adapters opt in), a DebuggerMemoryMap state cache that is
    marked dirty on every stop and fetched lazily, and
    DebuggerController::GetMemoryMap().
  - adapters: LLDB (SBProcess::GetMemoryRegions) and GDB (/proc/[pid]/maps,
    parsed per region instead of aggregated by path as GetModuleList does).
  - FFI: BNDebugMemoryRegion + BNDebuggerGetMemoryMap/FreeMemoryRegions.
  - C++ API + Python (`controller.memory_map`).

This is the data foundation for #96/#27. It deliberately does not yet
change the debugger's live BinaryView: the flat remote region added in
CreateDebuggerBinaryView still backs reads, so readability is unaffected.
Replacing that flat region with per-region segments (lengths + rwx) is a
follow-up, to be landed only after the map is validated as complete on each
platform so that no readable byte becomes unreadable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a "Debugger Memory Map" sidebar panel that lists the target's mapped
regions (Start / End / Size / Permissions / Name), mirroring the existing
Debugger Modules panel. It refreshes on the target-stopped event via
DebuggerController::GetMemoryMap(), supports filtering, copy/copy-all, and
double-click / context-menu navigation to a region's start or end.

This gives a UI to exercise and validate the memory-map data added in the
previous commit before the per-region-segment follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enumerate the target's committed virtual-memory regions via
IDebugDataSpaces2::QueryVirtual (the DbgEng wrapper over VirtualQueryEx),
walking the address space from 0 and advancing by each region's size until
QueryVirtual reports the end.

Only MEM_COMMIT regions that are neither guard nor no-access pages are
reported. Permissions are derived from the page Protect flags; MEM_MAPPED
regions are marked shared; and image-backed (MEM_IMAGE) regions get their
backing module's image path as the name via GetModuleByOffset/
GetModuleNames.

The DbgEng TTD and kernel adapters inherit this via DbgEngAdapter. The
non-DbgEng WindowsNativeAdapter still uses the default empty map for now.

Note: this adapter only builds on Windows, so it is validated by CI rather
than the local (macOS) build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enumerate the target's committed virtual-memory regions with VirtualQueryEx
on the process handle, walking the address space from 0 and advancing by
each region's size until the query reports the end.

Mirrors the DbgEng implementation: only MEM_COMMIT regions that are neither
guard nor no-access pages are reported; permissions come from the page
Protect flags; MEM_MAPPED regions are marked shared; and image/file-backed
regions get their backing file path via the existing GetModuleNameFromHandle
helper (the "<unknown>" sentinel is mapped to an empty name for anonymous
mappings).

With this, all Windows adapters (DbgEng + TTD + kernel via inheritance, and
WindowsNative) report a memory map.

Note: this adapter only builds on Windows, so it is validated by CI rather
than the local (macOS) build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GdbMiAdapter derives directly from DebugAdapter (not GdbAdapter), so it did
not inherit the RSP adapter's memory map and was returning the empty
default. Implement it by parsing "info proc mappings" -- the same command it
already uses for GetModuleList -- but keeping every region and reading the
permissions.

The Perms column is present on GDB 8.0+ and its position can vary, so it is
detected by pattern rather than fixed index; on older GDB that omits it, the
region is still reported as readable. The trailing objfile/pseudo-name (e.g.
[stack]) is used as the region name.

Builds locally (this adapter is compiled on macOS).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add "Select In Binary View" context action: navigates to the region's
  start and selects its full [start, end) range in the current view.
- Add "Save Region To Disk..." context action: reads the region via
  ReadMemory and writes it to a user-chosen file, warning on partial
  reads and erroring when nothing is readable.
- Fix Copy so that a multi-cell selection copies every selected cell
  (grouped by row, tab-separated columns, newline-separated rows, in
  visual order) instead of only the first cell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xusheng6 xusheng6 force-pushed the test_implement_96_memory_map branch from e1e7a43 to 7ee5bc5 Compare July 8, 2026 16:58
@xusheng6 xusheng6 merged commit e4b8694 into dev Jul 8, 2026
2 checks passed
@xusheng6 xusheng6 deleted the test_implement_96_memory_map branch July 8, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant